#include "CPU.h"

CPU::CPU(sc_module_name name_,
		 unsigned		Main_DMA_PRD_start_addr_,
		 unsigned		SSD_DMA_PRD_start_addr_,
		 unsigned		DMA_cmd_reg_start_addr_,
		 unsigned		System_type_,
		 unsigned		Experiment_type_,
		 unsigned		PRD_length_,
		 unsigned		DMA_command_)
	: sc_module					(name_)
	, Main_DMA_PRD_start_addr	(0x0, Main_DMA_PRD_start_addr_)
	, SSD_DMA_PRD_start_addr	(0x0, SSD_DMA_PRD_start_addr_)
	, DMA_cmd_reg_start_addr	(0x0, DMA_cmd_reg_start_addr_)
	, System_type				(System_type_)
	, Experiment_type			(Experiment_type_)
	, PRD_length				(PRD_length_)
	, DMA_command		 		(DMA_command_)
{
	srand((unsigned)time(NULL));

	SC_THREAD(CPU_action);
	dont_initialize();
	sensitive << CLK;
}

CPU::~CPU()
{
}

void
CPU::CPU_action()
{
	while (true)
	{
		CPU_data_t* data;
		CPU_data_t read_data;

		bool rand_rw = true;
		CPU_addr_t rand_addr;

		unsigned cmd_len;

		switch (Experiment_type)
		{
		case 1:
//#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> SYSTEM type = " << System_type << endl;
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> EXPERIMENT type = " << Experiment_type << ", PRD length = " << PRD_length << ", DMA command = 0x" << hex << DMA_command << dec << endl;
//#endif
#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> DMA PRD Setting to address 0x" << hex << SSD_DMA_PRD_start_addr.low_address << dec << endl;
#endif
			data = new CPU_data_t[PRD_length];
			for (int i=0; i<PRD_length; i++)
			{
				data[i].lower_bytes = 0x1000 * i;
				if (i!=PRD_length-1)
					data[i].upper_bytes = 0x80001000;
				else
					data[i].upper_bytes = 0x00001000;
			}
			system_port->CPU_init_transaction(true, SSD_DMA_PRD_start_addr, PRD_length);
			wait();
			for (int i=0;i<PRD_length;i++)
			{
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
				system_port->CPU_write_data(data[i]);
				wait();
			}
			system_port->CPU_end_transaction();
			delete[] data;
			wait();
			while (!system_port->CPU_is_done())
				wait();
#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Send DMA command to address 0x" << hex << DMA_cmd_reg_start_addr.low_address << dec << endl;
#endif
			if ((System_type == 1) || (System_type == 2))
			{
				cmd_len = 2;
				data = new CPU_data_t[cmd_len];
				data[0].lower_bytes = DMA_command;
				data[0].upper_bytes = 8*PRD_length; // SECTOR count : 512B * i
				data[1].lower_bytes = 0x00; // LBA
				data[1].upper_bytes = PRD_length; // PRD length by khbang
			}
			else
			{
				cmd_len = 4;
				data = new CPU_data_t[cmd_len];
				data[0].lower_bytes = 0x00; // No Main DMA
				data[0].upper_bytes = 0x00; // SECTOR count : 512B * i
				data[1].lower_bytes = 0x00; // LBA
				data[1].upper_bytes = 0;

				data[2].lower_bytes = DMA_command;
				data[2].upper_bytes = 8*PRD_length; // SECTOR count : 512B * i
				data[3].lower_bytes = 0x00; // LBA
				data[3].upper_bytes = PRD_length; // PRD length by khbang
			}
			system_port->CPU_init_transaction(true, DMA_cmd_reg_start_addr, cmd_len);
			wait();
			for (int i=0;i<cmd_len;i++)
			{
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
				system_port->CPU_write_data(data[i]);
				wait();
			}
			system_port->CPU_end_transaction();
			delete[] data;
			wait();
			while (!system_port->CPU_is_done())
				wait();
			while (!system_port->CPU_check_interrupt(System_type))
				wait();
//#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> DMA is finished" << endl;
//#endif
			break;
		case 2:
//#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> SYSTEM type = " << System_type << endl;
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> EXPERIMENT type = " << Experiment_type << ", PRD length = " << PRD_length << endl;
//#endif
#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> DMA PRD Setting to address 0x" << hex << Main_DMA_PRD_start_addr.low_address << dec << endl;
#endif
			data = new CPU_data_t[PRD_length];
			for (int i=0; i<PRD_length; i++)
			{
				data[i].lower_bytes = 0x1000 * i;
				if (i!=PRD_length-1)
					data[i].upper_bytes = 0x80001000;
				else
					data[i].upper_bytes = 0x00001000;
			}
			system_port->CPU_init_transaction(true, Main_DMA_PRD_start_addr, PRD_length);
			wait();
			for (int i=0;i<PRD_length;i++)
			{
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
				system_port->CPU_write_data(data[i]);
				wait();
			}
			system_port->CPU_end_transaction();
			delete[] data;
			wait();
			while (!system_port->CPU_is_done())
				wait();

			if ((System_type == 1) || (System_type == 2))
			{
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Send DMA command to address 0x" << hex << DMA_cmd_reg_start_addr.low_address << dec << endl;
#endif
				data = new CPU_data_t[2];
				data[0].lower_bytes = 0xCA;
				data[0].upper_bytes = 8*PRD_length; // SECTOR count : 512B * i
				data[1].lower_bytes = 0x00; // LBA
				data[1].upper_bytes = PRD_length; // PRD length by khbang

				system_port->CPU_init_transaction(true, DMA_cmd_reg_start_addr, 2);
				wait();
				for (int i=0;i<2;i++)
				{
#ifdef	DEBUG_CPU
					cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
					system_port->CPU_write_data(data[i]);
					wait();
				}
				system_port->CPU_end_transaction();
				delete[] data;
				wait();
				while (!system_port->CPU_is_done())
					wait();
				while (!system_port->CPU_check_interrupt(System_type))
					wait();
//#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> DMA is finished" << endl;
//#endif
//#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> PRD length = " << PRD_length << ", DMA command = 0xC8" << endl;
//#endif
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> DMA PRD Setting to address 0x" << hex << Main_DMA_PRD_start_addr.low_address << dec << endl;
#endif
				data = new CPU_data_t[PRD_length];
				for (int i=0; i<PRD_length; i++)
				{
					data[i].lower_bytes = 0x1000 * i;
					if (i!=PRD_length-1)
						data[i].upper_bytes = 0x80001000;
					else
						data[i].upper_bytes = 0x00001000;
				}
				system_port->CPU_init_transaction(true, Main_DMA_PRD_start_addr, PRD_length);
				wait();
				for (int i=0;i<PRD_length;i++)
				{
#ifdef	DEBUG_CPU
					cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
					system_port->CPU_write_data(data[i]);
					wait();
				}
				system_port->CPU_end_transaction();
				delete[] data;
				wait();
				while (!system_port->CPU_is_done())
					wait();
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Send DMA command to address 0x" << hex << DMA_cmd_reg_start_addr.low_address << dec << endl;
#endif
				data = new CPU_data_t[2];
				data[0].lower_bytes = 0xC8;
				data[0].upper_bytes = 8*PRD_length; // SECTOR count : 512B * i
				data[1].lower_bytes = 0x00 + 8*PRD_length; // LBA
				data[1].upper_bytes = PRD_length; // PRD length by khbang

				system_port->CPU_init_transaction(true, DMA_cmd_reg_start_addr, 2);
				wait();
				for (int i=0;i<2;i++)
				{
#ifdef	DEBUG_CPU
					cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
					system_port->CPU_write_data(data[i]);
					wait();
				}
				system_port->CPU_end_transaction();
				delete[] data;
				wait();
				while (!system_port->CPU_is_done())
					wait();
				while (!system_port->CPU_check_interrupt(System_type))
					wait();
			}
			else
			{
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> DMA PRD Setting to address 0x" << hex << SSD_DMA_PRD_start_addr.low_address << dec << endl;
#endif
				data = new CPU_data_t[PRD_length];
				for (int i=0; i<PRD_length; i++)
				{
					data[i].lower_bytes = 0x1000 * i;
					if (i!=PRD_length-1)
						data[i].upper_bytes = 0x80001000;
					else
						data[i].upper_bytes = 0x00001000;
				}
				system_port->CPU_init_transaction(true, SSD_DMA_PRD_start_addr, PRD_length);
				wait();
				for (int i=0;i<PRD_length;i++)
				{
#ifdef	DEBUG_CPU
					cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
					system_port->CPU_write_data(data[i]);
					wait();
				}
				system_port->CPU_end_transaction();
				delete[] data;
				wait();
				while (!system_port->CPU_is_done())
					wait();
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Send DMA command to address 0x" << hex << DMA_cmd_reg_start_addr.low_address << dec << endl;
#endif
				data = new CPU_data_t[4];
				data[0].lower_bytes = 0xC8;
				data[0].upper_bytes = 8*PRD_length; // SECTOR count : 512B * i
				data[1].lower_bytes = 0x00; // LBA
				data[1].upper_bytes = PRD_length; // PRD length by khbang
				data[2].lower_bytes = 0xCA;
				data[2].upper_bytes = 8*PRD_length; // SECTOR count : 512B * i
				data[3].lower_bytes = 0x00+0x1000*8*PRD_length; // LBA
				data[3].upper_bytes = PRD_length; // PRD length by khbang

				system_port->CPU_init_transaction(true, DMA_cmd_reg_start_addr, 4);
				wait();
				for (int i=0;i<4;i++)
				{
#ifdef	DEBUG_CPU
					cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
					system_port->CPU_write_data(data[i]);
					wait();
				}
				system_port->CPU_end_transaction();
				delete[] data;
				wait();
				while (!system_port->CPU_is_done())
					wait();
				while (!system_port->CPU_check_interrupt(System_type))
					wait();
			}
//#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> DMA is finished" << endl;
//#endif
			break;
		case 3:
//#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> SYSTEM type = " << System_type << endl;
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> EXPERIMENT type = " << Experiment_type << ", PRD length = " << PRD_length << ", DMA command = 0x" << hex << DMA_command << dec << endl;
//#endif
#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> DMA PRD Setting to address 0x" << hex << SSD_DMA_PRD_start_addr.low_address << dec << endl;
#endif
			data = new CPU_data_t[PRD_length];
			for (int i=0; i<PRD_length; i++)
			{
				data[i].lower_bytes = 0x1000 * i;
				if (i!=PRD_length-1)
					data[i].upper_bytes = 0x80001000;
				else
					data[i].upper_bytes = 0x00001000;
			}
			system_port->CPU_init_transaction(true, SSD_DMA_PRD_start_addr, PRD_length);
			wait();
			for (int i=0;i<PRD_length;i++)
			{
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
				system_port->CPU_write_data(data[i]);
				wait();
			}
			system_port->CPU_end_transaction();
			delete[] data;
			wait();
			while (!system_port->CPU_is_done())
				wait();
#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Send DMA command to address 0x" << hex << DMA_cmd_reg_start_addr.low_address << dec << endl;
#endif
			if ((System_type == 1) || (System_type == 2))
			{
				cmd_len = 2;
				data = new CPU_data_t[cmd_len];
				data[0].lower_bytes = DMA_command;
				data[0].upper_bytes = 8*PRD_length; // SECTOR count : 512B * i
				data[1].lower_bytes = 0x00; // LBA
				data[1].upper_bytes = PRD_length; // PRD length by khbang
			}
			else
			{
				cmd_len = 4;
				data = new CPU_data_t[cmd_len];
				data[0].lower_bytes = 0x00; // No Main DMA
				data[0].upper_bytes = 0x00; // SECTOR count : 512B * i
				data[1].lower_bytes = 0x00; // LBA
				data[1].upper_bytes = 0;

				data[2].lower_bytes = DMA_command;
				data[2].upper_bytes = 8*PRD_length; // SECTOR count : 512B * i
				data[3].lower_bytes = 0x00; // LBA
				data[3].upper_bytes = PRD_length; // PRD length by khbang
			}
			system_port->CPU_init_transaction(true, DMA_cmd_reg_start_addr, cmd_len);
			wait();
			for (int i=0;i<cmd_len;i++)
			{
#ifdef	DEBUG_CPU
				cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
				system_port->CPU_write_data(data[i]);
				wait();
			}
			system_port->CPU_end_transaction();
			delete[] data;
			wait();
			while (!system_port->CPU_is_done())
				wait();
			while (!system_port->CPU_check_interrupt(System_type))
			{
				rand_addr.high_address = 0x0;
				rand_addr.low_address = (rand()%4096)<<16 | (rand()%256)<<8 | (rand()%8)<<5;
				PRD_length = rand()%512;
				if (rand_addr.low_address + 8*PRD_length > 0x10000000)
					PRD_length = 0x10000000- rand_addr.low_address;
				else if (PRD_length == 0)
					PRD_length = 1;
#ifdef	DEBUG_CPU
				if (rand_rw)
				{
					cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Randow Memory WRITE for Random address 0x" << hex << rand_addr.low_address << "(" << ((rand_addr.low_address>>5) & 0x1) << "," << ((rand_addr.low_address>>6) & 0x3) << "," << ((rand_addr.low_address>>16) & 0xFFFF) << "," << (((rand_addr.low_address>>6) & 0x3FC) | ((rand_addr.low_address>>3) & 0x3)) << ")" << dec << endl;
					cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Randow Memory WRITE for Random length " << 8*PRD_length << endl;
				}
				else
				{
					cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Randow Memory READ for Random address 0x" << hex << rand_addr.low_address << "(" << ((rand_addr.low_address>>5) & 0x1) << "," << ((rand_addr.low_address>>6) & 0x3) << "," << ((rand_addr.low_address>>16) & 0xFFFF) << "," << (((rand_addr.low_address>>6) & 0x3FC) | ((rand_addr.low_address>>3) & 0x3)) << ")" << dec << endl;
					cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Randow Memory READ for Random length " << 8*PRD_length << endl;
				}
#endif
				if (rand_rw)
				{
					data = new CPU_data_t[PRD_length];
					data[0].lower_bytes = data[0].upper_bytes = 0;
					for (int i=1; i<PRD_length; i++)
					{
						data[i].lower_bytes = data[i-1].lower_bytes+1;
						data[i].upper_bytes = data[i-1].upper_bytes+1;
					}
				}
				system_port->CPU_init_transaction(rand_rw, rand_addr, PRD_length);
				wait();
				if (rand_rw)
				{
					for (int i=0;i<PRD_length;i++)
					{
#ifdef	DEBUG_CPU
						cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Write data : 0x" << hex << data[i].upper_bytes << " | 0x" << data[i].lower_bytes << dec << endl;
#endif
						system_port->CPU_write_data(data[i]);
						wait();
					}
					system_port->CPU_end_transaction();
					delete[] data;
					wait();
					while (!system_port->CPU_is_done())
						wait();
				}
				else
				{
					while(!system_port->CPU_is_ready())
						wait();
					for (int i=0;i<PRD_length;i++)
					{
#ifdef	DEBUG_CPU
						cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> Before Read data : 0x" << hex << read_data.upper_bytes << " | 0x" << read_data.lower_bytes << dec;
#endif
						read_data = system_port->CPU_read_data();
#ifdef	DEBUG_CPU
						cout << ", After Read data : 0x" << hex << read_data.upper_bytes << " | 0x" << read_data.lower_bytes << dec << endl;
#endif
						wait();
					}
					system_port->CPU_end_transaction();
				}
				rand_rw = !rand_rw;
				wait();
				wait();
			}
//#ifdef	DEBUG_CPU
			cout << "[" << sc_time_stamp() << "] " << sc_module::name() << "> DMA is finished" << endl;
//#endif
			break;
		case 4:
			break;
		default:
			cout << " Unknown EXPERIMENT!" << endl;
		}

		while (true)
			wait();
	}
}